package com.dosgi.classloader;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.dosgi.IDosgiContext;
import com.dosgi.module.IModuleContext;

/**
 * 配置实现类加载器
 * 
 * @author dingnate
 *
 */
public class DosgiClassLoader extends URLClassLoader {
	private final ConcurrentHashMap<String, Object> parallelLockMap = new ConcurrentHashMap<String, Object>();;
	private IModuleContext mContext;
	private ClassLoader parent;
	public static ClassLoader BOOT_CLASSLOADER = new ClassLoader(
			Object.class.getClassLoader()) {/*
											 * boot classloader
											 */
	};

	public DosgiClassLoader(IModuleContext mContext, URL[] urls,
			ClassLoader parent) {
		super(urls, parent);
		this.parent = parent != null ? parent : BOOT_CLASSLOADER;
		this.mContext = mContext;
	}

	@Override
	protected Class<?> loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		synchronized (getClassLoadingLock(name)) {
			// find class in parent for java package class
			if (name.startsWith("java."))
				return parent.loadClass(name);
			Class<?> c = findClassInternal(name);
			if (c == null)
				c = parent.loadClass(name);
			if (resolve)
				resolveClass(c);
			return c;
		}
	}

	private Class<?> findClassInternal(String name)
			throws ClassNotFoundException {
		Class<?> c = findLoadedClass(name);
		if (c == null && mContext != null) {
			IDosgiContext containerCtx = mContext.getContainerCtx();
			if (c == null) {
				// find class in import packages
				IModuleContext requireModule = getRequireModuleInImportPackages(
						containerCtx, name);
				if (requireModule != null) {
					DosgiClassLoader classLoader = (DosgiClassLoader) requireModule
							.getClassLoader();
					try {
						c = classLoader.findClassInternal(name);
					} catch (ClassNotFoundException e) {
						// do nothing continue load class
					}
				}
			}
			if (c == null) {
				// find class in required modules
				for (IModuleContext requireModule : mContext.getResolvedRequireModules()) {
					DosgiClassLoader classLoader = (DosgiClassLoader) requireModule.getClassLoader();
					try {
						c = classLoader.findClassInternal(name);
					} catch (ClassNotFoundException e) {
						// do nothing continue load class
					}
					if (c != null)
						break;
				}
			}
		}
		if (c == null)
			try {
				c = findClass(name);
			} catch (ClassNotFoundException e) {
				// do nothing continue load class
			}
		return c;
	}

	private IModuleContext getRequireModuleInImportPackages(
			IDosgiContext containerCtx, String name) {
		String packageName = getPackageName(name);
		Map<String, String> importPackages = mContext
				.getImportPackages();
		if (importPackages.containsKey(packageName)) {
			String version = importPackages.get(packageName);
			return containerCtx.getModuleByExportPackage(packageName, version);
		}
		return null;
	}

	@Override
	public URL getResource(String name) {
		if (name.startsWith("java/"))
			return parent.getResource(name);
		URL url = findResourceInternal(name);
		if (url == null)
			url = parent.getResource(name);
		return url;
	}

	private URL findResourceInternal(String name) {
		URL url = null;
		if (mContext != null) {
			IDosgiContext containerCtx = mContext.getContainerCtx();
			if (url == null) {
				// find in declared import packages
				IModuleContext requireModule = getRequireModuleInImportPackages(
						containerCtx, name);
				if (requireModule != null) {
					url = ((DosgiClassLoader) requireModule.getClassLoader())
							.findResourceInternal(name);
				}
			}
			if (url == null) {
				// find in required modules
				for (IModuleContext requireModule : mContext.getResolvedRequireModules()) {
					url = ((DosgiClassLoader) requireModule.getClassLoader()).findResourceInternal(name);
					if (url != null)
						break;
				}
			}
		}
		if (url == null)
			url = findResource(name);
		return url;
	}

	protected Object getClassLoadingLock(String className) {
		Object lock = parallelLockMap.get(className);
		if (lock != null)
			return lock;
		Object newLock = new Object();
		lock = parallelLockMap.putIfAbsent(className, newLock);
		if (lock == null)
			lock = newLock;
		return lock;
	}

	public final static String getPackageName(String name) {
		if (name != null) {
			int index = name.lastIndexOf('.'); // find last period in class name
			if (index > 0)
				return name.substring(0, index);
		}
		return ".";
	}
}